/***
 * ASM: a very small and fast Java bytecode manipulation framework
 * Copyright (c) 2000-2011 INRIA, France Telecom
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the copyright holders nor the names of its
 *    contributors may be used to endorse or promote products derived from
 *    this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
 * THE POSSIBILITY OF SUCH DAMAGE.
 */
package org.springframework.asm;

import java.io.IOException;
import java.io.InputStream;

/**
 * A Java class parser to make a {@link ClassVisitor} visit an existing class.
 * This class parses a byte array conforming to the Java class file format and
 * calls the appropriate visit methods of a given class visitor for each field,
 * method and bytecode instruction encountered.
 *
 * @author Eric Bruneton
 * @author Eugene Kuleshov
 */
public class ClassReader {

	/**
	 * True to enable signatures support.
	 */
	static final boolean SIGNATURES = true;

	/**
	 * True to enable annotations support.
	 */
	static final boolean ANNOTATIONS = true;

	/**
	 * True to enable stack map frames support.
	 */
	static final boolean FRAMES = true;

	/**
	 * True to enable bytecode writing support.
	 */
	static final boolean WRITER = true;

	/**
	 * True to enable JSR_W and GOTO_W support.
	 */
	static final boolean RESIZE = true;

	/**
	 * Flag to skip method code. If this class is set <code>CODE</code>
	 * attribute won't be visited. This can be used, for example, to retrieve
	 * annotations for methods and method parameters.
	 */
	public static final int SKIP_CODE = 1;

	/**
	 * Flag to skip the debug information in the class. If this flag is set the
	 * debug information of the class is not visited, i.e. the
	 * {@link MethodVisitor#visitLocalVariable visitLocalVariable} and
	 * {@link MethodVisitor#visitLineNumber visitLineNumber} methods will not be
	 * called.
	 */
	public static final int SKIP_DEBUG = 2;

	/**
	 * Flag to skip the stack map frames in the class. If this flag is set the
	 * stack map frames of the class is not visited, i.e. the
	 * {@link MethodVisitor#visitFrame visitFrame} method will not be called.
	 * This flag is useful when the {@link ClassWriter#COMPUTE_FRAMES} option is
	 * used: it avoids visiting frames that will be ignored and recomputed from
	 * scratch in the class writer.
	 */
	public static final int SKIP_FRAMES = 4;

	/**
	 * Flag to expand the stack map frames. By default stack map frames are
	 * visited in their original format (i.e. "expanded" for classes whose
	 * version is less than V1_6, and "compressed" for the other classes). If
	 * this flag is set, stack map frames are always visited in expanded format
	 * (this option adds a decompression/recompression step in ClassReader and
	 * ClassWriter which degrades performances quite a lot).
	 */
	public static final int EXPAND_FRAMES = 8;

	/**
	 * Flag to expand the ASM pseudo instructions into an equivalent sequence of
	 * standard bytecode instructions. When resolving a forward jump it may
	 * happen that the signed 2 bytes offset reserved for it is not sufficient
	 * to store the bytecode offset. In this case the jump instruction is
	 * replaced with a temporary ASM pseudo instruction using an unsigned 2
	 * bytes offset (see Label#resolve). This internal flag is used to re-read
	 * classes containing such instructions, in order to replace them with
	 * standard instructions. In addition, when this flag is used, GOTO_W and
	 * JSR_W are <i>not</i> converted into GOTO and JSR, to make sure that
	 * infinite loops where a GOTO_W is replaced with a GOTO in ClassReader and
	 * converted back to a GOTO_W in ClassWriter cannot occur.
	 */
	static final int EXPAND_ASM_INSNS = 256;

	/**
	 * The class to be parsed. <i>The content of this array must not be
	 * modified. This field is intended for {@link Attribute} sub classes, and
	 * is normally not needed by class generators or adapters.</i>
	 */
	public final byte[] b;

	/**
	 * The start index of each constant pool item in {@link #b b}, plus one. The
	 * one byte offset skips the constant pool item tag that indicates its type.
	 */
	private final int[] items;

	/**
	 * The String objects corresponding to the CONSTANT_Utf8 items. This cache
	 * avoids multiple parsing of a given CONSTANT_Utf8 constant pool item,
	 * which GREATLY improves performances (by a factor 2 to 3). This caching
	 * strategy could be extended to all constant pool items, but its benefit
	 * would not be so great for these items (because they are much less
	 * expensive to parse than CONSTANT_Utf8 items).
	 */
	private final String[] strings;

	/**
	 * Maximum length of the strings contained in the constant pool of the
	 * class.
	 */
	private final int maxStringLength;

	/**
	 * Start index of the class header information (access, name...) in
	 * {@link #b b}.
	 */
	public final int header;

	// ------------------------------------------------------------------------
	// Constructors
	// ------------------------------------------------------------------------

	/**
	 * Constructs a new {@link ClassReader} object.
	 *
	 * @param b the bytecode of the class to be read.
	 */
	public ClassReader(final byte[] b) {
		this(b, 0, b.length);
	}

	/**
	 * Constructs a new {@link ClassReader} object.
	 *
	 * @param b   the bytecode of the class to be read.
	 * @param off the start offset of the class data.
	 * @param len the length of the class data.
	 */
	public ClassReader(final byte[] b, final int off, final int len) {
		this.b = b;
		// checks the class version
		/* SPRING PATCH: REMOVED FOR FORWARD COMPATIBILITY WITH JDK 9
        if (readShort(off + 6) > Opcodes.V1_8) {
            throw new IllegalArgumentException();
        }
		*/
		// parses the constant pool
		items = new int[readUnsignedShort(off + 8)];
		int n = items.length;
		strings = new String[n];
		int max = 0;
		int index = off + 10;
		for (int i = 1; i < n; ++i) {
			items[i] = index + 1;
			int size;
			switch (b[index]) {
				case ClassWriter.FIELD:
				case ClassWriter.METH:
				case ClassWriter.IMETH:
				case ClassWriter.INT:
				case ClassWriter.FLOAT:
				case ClassWriter.NAME_TYPE:
				case ClassWriter.INDY:
					size = 5;
					break;
				case ClassWriter.LONG:
				case ClassWriter.DOUBLE:
					size = 9;
					++i;
					break;
				case ClassWriter.UTF8:
					size = 3 + readUnsignedShort(index + 1);
					if (size > max) {
						max = size;
					}
					break;
				case ClassWriter.HANDLE:
					size = 4;
					break;
				// case ClassWriter.CLASS:
				// case ClassWriter.STR:
				// case ClassWriter.MTYPE
				// case ClassWriter.PACKAGE:
				// case ClassWriter.MODULE:
				default:
					size = 3;
					break;
			}
			index += size;
		}
		maxStringLength = max;
		// the class header information starts just after the constant pool
		header = index;
	}

	/**
	 * Returns the class's access flags (see {@link Opcodes}). This value may
	 * not reflect Deprecated and Synthetic flags when bytecode is before 1.5
	 * and those flags are represented by attributes.
	 *
	 * @return the class access flags
	 * @see ClassVisitor#visit(int, int, String, String, String, String[])
	 */
	public int getAccess() {
		return readUnsignedShort(header);
	}

	/**
	 * Returns the internal name of the class (see
	 * {@link Type#getInternalName() getInternalName}).
	 *
	 * @return the internal class name
	 * @see ClassVisitor#visit(int, int, String, String, String, String[])
	 */
	public String getClassName() {
		return readClass(header + 2, new char[maxStringLength]);
	}

	/**
	 * Returns the internal of name of the super class (see
	 * {@link Type#getInternalName() getInternalName}). For interfaces, the
	 * super class is {@link Object}.
	 *
	 * @return the internal name of super class, or <tt>null</tt> for
	 * {@link Object} class.
	 * @see ClassVisitor#visit(int, int, String, String, String, String[])
	 */
	public String getSuperName() {
		return readClass(header + 4, new char[maxStringLength]);
	}

	/**
	 * Returns the internal names of the class's interfaces (see
	 * {@link Type#getInternalName() getInternalName}).
	 *
	 * @return the array of internal names for all implemented interfaces or
	 * <tt>null</tt>.
	 * @see ClassVisitor#visit(int, int, String, String, String, String[])
	 */
	public String[] getInterfaces() {
		int index = header + 6;
		int n = readUnsignedShort(index);
		String[] interfaces = new String[n];
		if (n > 0) {
			char[] buf = new char[maxStringLength];
			for (int i = 0; i < n; ++i) {
				index += 2;
				interfaces[i] = readClass(index, buf);
			}
		}
		return interfaces;
	}

	/**
	 * Copies the constant pool data into the given {@link ClassWriter}. Should
	 * be called before the {@link #accept(ClassVisitor, int)} method.
	 *
	 * @param classWriter the {@link ClassWriter} to copy constant pool into.
	 */
	void copyPool(final ClassWriter classWriter) {
		char[] buf = new char[maxStringLength];
		int ll = items.length;
		Item[] items2 = new Item[ll];
		for (int i = 1; i < ll; i++) {
			int index = items[i];
			int tag = b[index - 1];
			Item item = new Item(i);
			int nameType;
			switch (tag) {
				case ClassWriter.FIELD:
				case ClassWriter.METH:
				case ClassWriter.IMETH:
					nameType = items[readUnsignedShort(index + 2)];
					item.set(tag, readClass(index, buf), readUTF8(nameType, buf),
							readUTF8(nameType + 2, buf));
					break;
				case ClassWriter.INT:
					item.set(readInt(index));
					break;
				case ClassWriter.FLOAT:
					item.set(Float.intBitsToFloat(readInt(index)));
					break;
				case ClassWriter.NAME_TYPE:
					item.set(tag, readUTF8(index, buf), readUTF8(index + 2, buf),
							null);
					break;
				case ClassWriter.LONG:
					item.set(readLong(index));
					++i;
					break;
				case ClassWriter.DOUBLE:
					item.set(Double.longBitsToDouble(readLong(index)));
					++i;
					break;
				case ClassWriter.UTF8: {
					String s = strings[i];
					if (s == null) {
						index = items[i];
						s = strings[i] = readUTF(index + 2,
								readUnsignedShort(index), buf);
					}
					item.set(tag, s, null, null);
					break;
				}
				case ClassWriter.HANDLE: {
					int fieldOrMethodRef = items[readUnsignedShort(index + 1)];
					nameType = items[readUnsignedShort(fieldOrMethodRef + 2)];
					item.set(ClassWriter.HANDLE_BASE + readByte(index),
							readClass(fieldOrMethodRef, buf),
							readUTF8(nameType, buf), readUTF8(nameType + 2, buf));
					break;
				}
				case ClassWriter.INDY:
					if (classWriter.bootstrapMethods == null) {
						copyBootstrapMethods(classWriter, items2, buf);
					}
					nameType = items[readUnsignedShort(index + 2)];
					item.set(readUTF8(nameType, buf), readUTF8(nameType + 2, buf),
							readUnsignedShort(index));
					break;
				// case ClassWriter.STR:
				// case ClassWriter.CLASS:
				// case ClassWriter.MTYPE:
				// case ClassWriter.MODULE:
				// case ClassWriter.PACKAGE:
				default:
					item.set(tag, readUTF8(index, buf), null, null);
					break;
			}

			int index2 = item.hashCode % items2.length;
			item.next = items2[index2];
			items2[index2] = item;
		}

		int off = items[1] - 1;
		classWriter.pool.putByteArray(b, off, header - off);
		classWriter.items = items2;
		classWriter.threshold = (int) (0.75d * ll);
		classWriter.index = ll;
	}

	/**
	 * Copies the bootstrap method data into the given {@link ClassWriter}.
	 * Should be called before the {@link #accept(ClassVisitor, int)} method.
	 *
	 * @param classWriter the {@link ClassWriter} to copy bootstrap methods into.
	 */
	private void copyBootstrapMethods(final ClassWriter classWriter,
									  final Item[] items, final char[] c) {
		// finds the "BootstrapMethods" attribute
		int u = getAttributes();
		boolean found = false;
		for (int i = readUnsignedShort(u); i > 0; --i) {
			String attrName = readUTF8(u + 2, c);
			if ("BootstrapMethods".equals(attrName)) {
				found = true;
				break;
			}
			u += 6 + readInt(u + 4);
		}
		if (!found) {
			return;
		}
		// copies the bootstrap methods in the class writer
		int boostrapMethodCount = readUnsignedShort(u + 8);
		for (int j = 0, v = u + 10; j < boostrapMethodCount; j++) {
			int position = v - u - 10;
			int hashCode = readConst(readUnsignedShort(v), c).hashCode();
			for (int k = readUnsignedShort(v + 2); k > 0; --k) {
				hashCode ^= readConst(readUnsignedShort(v + 4), c).hashCode();
				v += 2;
			}
			v += 4;
			Item item = new Item(j);
			item.set(position, hashCode & 0x7FFFFFFF);
			int index = item.hashCode % items.length;
			item.next = items[index];
			items[index] = item;
		}
		int attrSize = readInt(u + 4);
		ByteVector bootstrapMethods = new ByteVector(attrSize + 62);
		bootstrapMethods.putByteArray(b, u + 10, attrSize - 2);
		classWriter.bootstrapMethodsCount = boostrapMethodCount;
		classWriter.bootstrapMethods = bootstrapMethods;
	}

	/**
	 * Constructs a new {@link ClassReader} object.
	 *
	 * @param is an input stream from which to read the class.
	 * @throws IOException if a problem occurs during reading.
	 */
	public ClassReader(final InputStream is) throws IOException {
		this(readClass(is, false));
	}

	/**
	 * Constructs a new {@link ClassReader} object.
	 *
	 * @param name the binary qualified name of the class to be read.
	 * @throws IOException if an exception occurs during reading.
	 */
	public ClassReader(final String name) throws IOException {
		this(readClass(
				ClassLoader.getSystemResourceAsStream(name.replace('.', '/')
						+ ".class"), true));
	}

	/**
	 * Reads the bytecode of a class.
	 *
	 * @param is    an input stream from which to read the class.
	 * @param close true to close the input stream after reading.
	 * @return the bytecode read from the given input stream.
	 * @throws IOException if a problem occurs during reading.
	 */
	private static byte[] readClass(final InputStream is, boolean close)
			throws IOException {
		if (is == null) {
			throw new IOException("Class not found");
		}
		try {
			byte[] b = new byte[is.available()];
			int len = 0;
			while (true) {
				int n = is.read(b, len, b.length - len);
				if (n == -1) {
					if (len < b.length) {
						byte[] c = new byte[len];
						System.arraycopy(b, 0, c, 0, len);
						b = c;
					}
					return b;
				}
				len += n;
				if (len == b.length) {
					int last = is.read();
					if (last < 0) {
						return b;
					}
					byte[] c = new byte[b.length + 1000];
					System.arraycopy(b, 0, c, 0, len);
					c[len++] = (byte) last;
					b = c;
				}
			}
		} finally {
			if (close) {
				is.close();
			}
		}
	}

	// ------------------------------------------------------------------------
	// Public methods
	// ------------------------------------------------------------------------

	/**
	 * Makes the given visitor visit the Java class of this {@link ClassReader}
	 * . This class is the one specified in the constructor (see
	 * {@link #ClassReader(byte[]) ClassReader}).
	 *
	 * @param classVisitor the visitor that must visit this class.
	 * @param flags        option flags that can be used to modify the default behavior
	 *                     of this class. See {@link #SKIP_DEBUG}, {@link #EXPAND_FRAMES}
	 *                     , {@link #SKIP_FRAMES}, {@link #SKIP_CODE}.
	 */
	public void accept(final ClassVisitor classVisitor, final int flags) {
		accept(classVisitor, new Attribute[0], flags);
	}

	/**
	 * Makes the given visitor visit the Java class of this {@link ClassReader}.
	 * This class is the one specified in the constructor (see
	 * {@link #ClassReader(byte[]) ClassReader}).
	 *
	 * @param classVisitor the visitor that must visit this class.
	 * @param attrs        prototypes of the attributes that must be parsed during the
	 *                     visit of the class. Any attribute whose type is not equal to
	 *                     the type of one the prototypes will not be parsed: its byte
	 *                     array value will be passed unchanged to the ClassWriter.
	 *                     <i>This may corrupt it if this value contains references to
	 *                     the constant pool, or has syntactic or semantic links with a
	 *                     class element that has been transformed by a class adapter
	 *                     between the reader and the writer</i>.
	 * @param flags        option flags that can be used to modify the default behavior
	 *                     of this class. See {@link #SKIP_DEBUG}, {@link #EXPAND_FRAMES}
	 *                     , {@link #SKIP_FRAMES}, {@link #SKIP_CODE}.
	 */
	public void accept(final ClassVisitor classVisitor,
					   final Attribute[] attrs, final int flags) {
		int u = header; // current offset in the class file
		char[] c = new char[maxStringLength]; // buffer used to read strings

		Context context = new Context();
		context.attrs = attrs;
		context.flags = flags;
		context.buffer = c;

		// reads the class declaration
		int access = readUnsignedShort(u);
		String name = readClass(u + 2, c);
		String superClass = readClass(u + 4, c);
		String[] interfaces = new String[readUnsignedShort(u + 6)];
		u += 8;
		for (int i = 0; i < interfaces.length; ++i) {
			interfaces[i] = readClass(u, c);
			u += 2;
		}

		// reads the class attributes
		String signature = null;
		String sourceFile = null;
		String sourceDebug = null;
		String enclosingOwner = null;
		String enclosingName = null;
		String enclosingDesc = null;
		String moduleMainClass = null;
		int anns = 0;
		int ianns = 0;
		int tanns = 0;
		int itanns = 0;
		int innerClasses = 0;
		int module = 0;
		int packages = 0;
		Attribute attributes = null;

		u = getAttributes();
		for (int i = readUnsignedShort(u); i > 0; --i) {
			String attrName = readUTF8(u + 2, c);
			// tests are sorted in decreasing frequency order
			// (based on frequencies observed on typical classes)
			if ("SourceFile".equals(attrName)) {
				sourceFile = readUTF8(u + 8, c);
			} else if ("InnerClasses".equals(attrName)) {
				innerClasses = u + 8;
			} else if ("EnclosingMethod".equals(attrName)) {
				enclosingOwner = readClass(u + 8, c);
				int item = readUnsignedShort(u + 10);
				if (item != 0) {
					enclosingName = readUTF8(items[item], c);
					enclosingDesc = readUTF8(items[item] + 2, c);
				}
			} else if (SIGNATURES && "Signature".equals(attrName)) {
				signature = readUTF8(u + 8, c);
			} else if (ANNOTATIONS
					&& "RuntimeVisibleAnnotations".equals(attrName)) {
				anns = u + 8;
			} else if (ANNOTATIONS
					&& "RuntimeVisibleTypeAnnotations".equals(attrName)) {
				tanns = u + 8;
			} else if ("Deprecated".equals(attrName)) {
				access |= Opcodes.ACC_DEPRECATED;
			} else if ("Synthetic".equals(attrName)) {
				access |= Opcodes.ACC_SYNTHETIC
						| ClassWriter.ACC_SYNTHETIC_ATTRIBUTE;
			} else if ("SourceDebugExtension".equals(attrName)) {
				int len = readInt(u + 4);
				sourceDebug = readUTF(u + 8, len, new char[len]);
			} else if (ANNOTATIONS
					&& "RuntimeInvisibleAnnotations".equals(attrName)) {
				ianns = u + 8;
			} else if (ANNOTATIONS
					&& "RuntimeInvisibleTypeAnnotations".equals(attrName)) {
				itanns = u + 8;
			} else if ("Module".equals(attrName)) {
				module = u + 8;
			} else if ("ModuleMainClass".equals(attrName)) {
				moduleMainClass = readClass(u + 8, c);
			} else if ("ModulePackages".equals(attrName)) {
				packages = u + 10;
			} else if ("BootstrapMethods".equals(attrName)) {
				int[] bootstrapMethods = new int[readUnsignedShort(u + 8)];
				for (int j = 0, v = u + 10; j < bootstrapMethods.length; j++) {
					bootstrapMethods[j] = v;
					v += 2 + readUnsignedShort(v + 2) << 1;
				}
				context.bootstrapMethods = bootstrapMethods;
			} else {
				Attribute attr = readAttribute(attrs, attrName, u + 8,
						readInt(u + 4), c, -1, null);
				if (attr != null) {
					attr.next = attributes;
					attributes = attr;
				}
			}
			u += 6 + readInt(u + 4);
		}

		// visits the class declaration
		classVisitor.visit(readInt(items[1] - 7), access, name, signature,
				superClass, interfaces);

		// visits the source and debug info
		if ((flags & SKIP_DEBUG) == 0
				&& (sourceFile != null || sourceDebug != null)) {
			classVisitor.visitSource(sourceFile, sourceDebug);
		}

		// visits the module info and associated attributes
		if (module != 0) {
			readModule(classVisitor, context, module,
					moduleMainClass, packages);
		}

		// visits the outer class
		if (enclosingOwner != null) {
			classVisitor.visitOuterClass(enclosingOwner, enclosingName,
					enclosingDesc);
		}

		// visits the class annotations and type annotations
		if (ANNOTATIONS && anns != 0) {
			for (int i = readUnsignedShort(anns), v = anns + 2; i > 0; --i) {
				v = readAnnotationValues(v + 2, c, true,
						classVisitor.visitAnnotation(readUTF8(v, c), true));
			}
		}
		if (ANNOTATIONS && ianns != 0) {
			for (int i = readUnsignedShort(ianns), v = ianns + 2; i > 0; --i) {
				v = readAnnotationValues(v + 2, c, true,
						classVisitor.visitAnnotation(readUTF8(v, c), false));
			}
		}
		if (ANNOTATIONS && tanns != 0) {
			for (int i = readUnsignedShort(tanns), v = tanns + 2; i > 0; --i) {
				v = readAnnotationTarget(context, v);
				v = readAnnotationValues(v + 2, c, true,
						classVisitor.visitTypeAnnotation(context.typeRef,
								context.typePath, readUTF8(v, c), true));
			}
		}
		if (ANNOTATIONS && itanns != 0) {
			for (int i = readUnsignedShort(itanns), v = itanns + 2; i > 0; --i) {
				v = readAnnotationTarget(context, v);
				v = readAnnotationValues(v + 2, c, true,
						classVisitor.visitTypeAnnotation(context.typeRef,
								context.typePath, readUTF8(v, c), false));
			}
		}

		// visits the attributes
		while (attributes != null) {
			Attribute attr = attributes.next;
			attributes.next = null;
			classVisitor.visitAttribute(attributes);
			attributes = attr;
		}

		// visits the inner classes
		if (innerClasses != 0) {
			int v = innerClasses + 2;
			for (int i = readUnsignedShort(innerClasses); i > 0; --i) {
				classVisitor.visitInnerClass(readClass(v, c),
						readClass(v + 2, c), readUTF8(v + 4, c),
						readUnsignedShort(v + 6));
				v += 8;
			}
		}

		// visits the fields and methods
		u = header + 10 + 2 * interfaces.length;
		for (int i = readUnsignedShort(u - 2); i > 0; --i) {
			u = readField(classVisitor, context, u);
		}
		u += 2;
		for (int i = readUnsignedShort(u - 2); i > 0; --i) {
			u = readMethod(classVisitor, context, u);
		}

		// visits the end of the class
		classVisitor.visitEnd();
	}

	/**
	 * Reads the module attribute and visit it.
	 *
	 * @param classVisitor the current class visitor
	 * @param context      information about the class being parsed.
	 * @param u            start offset of the module attribute in the class file.
	 * @param mainClass    name of the main class of a module or null.
	 * @param packages     start offset of the concealed package attribute.
	 */
	private void readModule(final ClassVisitor classVisitor,
							final Context context, int u,
							final String mainClass, int packages) {

		char[] buffer = context.buffer;

		// reads module name, flags and version
		String name = readModule(u, buffer);
		int flags = readUnsignedShort(u + 2);
		String version = readUTF8(u + 4, buffer);
		u += 6;

		ModuleVisitor mv = classVisitor.visitModule(name, flags, version);
		if (mv == null) {
			return;
		}

		// module attributes (main class, packages)
		if (mainClass != null) {
			mv.visitMainClass(mainClass);
		}

		if (packages != 0) {
			for (int i = readUnsignedShort(packages - 2); i > 0; --i) {
				String packaze = readPackage(packages, buffer);
				mv.visitPackage(packaze);
				packages += 2;
			}
		}

		// reads requires
		u += 2;
		for (int i = readUnsignedShort(u - 2); i > 0; --i) {
			String module = readModule(u, buffer);
			int access = readUnsignedShort(u + 2);
			String requireVersion = readUTF8(u + 4, buffer);
			mv.visitRequire(module, access, requireVersion);
			u += 6;
		}

		// reads exports
		u += 2;
		for (int i = readUnsignedShort(u - 2); i > 0; --i) {
			String export = readPackage(u, buffer);
			int access = readUnsignedShort(u + 2);
			int exportToCount = readUnsignedShort(u + 4);
			u += 6;
			String[] tos = null;
			if (exportToCount != 0) {
				tos = new String[exportToCount];
				for (int j = 0; j < tos.length; ++j) {
					tos[j] = readModule(u, buffer);
					u += 2;
				}
			}
			mv.visitExport(export, access, tos);
		}

		// reads opens
		u += 2;
		for (int i = readUnsignedShort(u - 2); i > 0; --i) {
			String open = readPackage(u, buffer);
			int access = readUnsignedShort(u + 2);
			int openToCount = readUnsignedShort(u + 4);
			u += 6;
			String[] tos = null;
			if (openToCount != 0) {
				tos = new String[openToCount];
				for (int j = 0; j < tos.length; ++j) {
					tos[j] = readModule(u, buffer);
					u += 2;
				}
			}
			mv.visitOpen(open, access, tos);
		}

		// read uses
		u += 2;
		for (int i = readUnsignedShort(u - 2); i > 0; --i) {
			mv.visitUse(readClass(u, buffer));
			u += 2;
		}

		// read provides
		u += 2;
		for (int i = readUnsignedShort(u - 2); i > 0; --i) {
			String service = readClass(u, buffer);
			int provideWithCount = readUnsignedShort(u + 2);
			u += 4;
			String[] withs = new String[provideWithCount];
			for (int j = 0; j < withs.length; ++j) {
				withs[j] = readClass(u, buffer);
				u += 2;
			}
			mv.visitProvide(service, withs);
		}

		mv.visitEnd();
	}

	/**
	 * Reads a field and makes the given visitor visit it.
	 *
	 * @param classVisitor the visitor that must visit the field.
	 * @param context      information about the class being parsed.
	 * @param u            the start offset of the field in the class file.
	 * @return the offset of the first byte following the field in the class.
	 */
	private int readField(final ClassVisitor classVisitor,
						  final Context context, int u) {
		// reads the field declaration
		char[] c = context.buffer;
		int access = readUnsignedShort(u);
		String name = readUTF8(u + 2, c);
		String desc = readUTF8(u + 4, c);
		u += 6;

		// reads the field attributes
		String signature = null;
		int anns = 0;
		int ianns = 0;
		int tanns = 0;
		int itanns = 0;
		Object value = null;
		Attribute attributes = null;

		for (int i = readUnsignedShort(u); i > 0; --i) {
			String attrName = readUTF8(u + 2, c);
			// tests are sorted in decreasing frequency order
			// (based on frequencies observed on typical classes)
			if ("ConstantValue".equals(attrName)) {
				int item = readUnsignedShort(u + 8);
				value = item == 0 ? null : readConst(item, c);
			} else if (SIGNATURES && "Signature".equals(attrName)) {
				signature = readUTF8(u + 8, c);
			} else if ("Deprecated".equals(attrName)) {
				access |= Opcodes.ACC_DEPRECATED;
			} else if ("Synthetic".equals(attrName)) {
				access |= Opcodes.ACC_SYNTHETIC
						| ClassWriter.ACC_SYNTHETIC_ATTRIBUTE;
			} else if (ANNOTATIONS
					&& "RuntimeVisibleAnnotations".equals(attrName)) {
				anns = u + 8;
			} else if (ANNOTATIONS
					&& "RuntimeVisibleTypeAnnotations".equals(attrName)) {
				tanns = u + 8;
			} else if (ANNOTATIONS
					&& "RuntimeInvisibleAnnotations".equals(attrName)) {
				ianns = u + 8;
			} else if (ANNOTATIONS
					&& "RuntimeInvisibleTypeAnnotations".equals(attrName)) {
				itanns = u + 8;
			} else {
				Attribute attr = readAttribute(context.attrs, attrName, u + 8,
						readInt(u + 4), c, -1, null);
				if (attr != null) {
					attr.next = attributes;
					attributes = attr;
				}
			}
			u += 6 + readInt(u + 4);
		}
		u += 2;

		// visits the field declaration
		FieldVisitor fv = classVisitor.visitField(access, name, desc,
				signature, value);
		if (fv == null) {
			return u;
		}

		// visits the field annotations and type annotations
		if (ANNOTATIONS && anns != 0) {
			for (int i = readUnsignedShort(anns), v = anns + 2; i > 0; --i) {
				v = readAnnotationValues(v + 2, c, true,
						fv.visitAnnotation(readUTF8(v, c), true));
			}
		}
		if (ANNOTATIONS && ianns != 0) {
			for (int i = readUnsignedShort(ianns), v = ianns + 2; i > 0; --i) {
				v = readAnnotationValues(v + 2, c, true,
						fv.visitAnnotation(readUTF8(v, c), false));
			}
		}
		if (ANNOTATIONS && tanns != 0) {
			for (int i = readUnsignedShort(tanns), v = tanns + 2; i > 0; --i) {
				v = readAnnotationTarget(context, v);
				v = readAnnotationValues(v + 2, c, true,
						fv.visitTypeAnnotation(context.typeRef,
								context.typePath, readUTF8(v, c), true));
			}
		}
		if (ANNOTATIONS && itanns != 0) {
			for (int i = readUnsignedShort(itanns), v = itanns + 2; i > 0; --i) {
				v = readAnnotationTarget(context, v);
				v = readAnnotationValues(v + 2, c, true,
						fv.visitTypeAnnotation(context.typeRef,
								context.typePath, readUTF8(v, c), false));
			}
		}

		// visits the field attributes
		while (attributes != null) {
			Attribute attr = attributes.next;
			attributes.next = null;
			fv.visitAttribute(attributes);
			attributes = attr;
		}

		// visits the end of the field
		fv.visitEnd();

		return u;
	}

	/**
	 * Reads a method and makes the given visitor visit it.
	 *
	 * @param classVisitor the visitor that must visit the method.
	 * @param context      information about the class being parsed.
	 * @param u            the start offset of the method in the class file.
	 * @return the offset of the first byte following the method in the class.
	 */
	private int readMethod(final ClassVisitor classVisitor,
						   final Context context, int u) {
		// reads the method declaration
		char[] c = context.buffer;
		context.access = readUnsignedShort(u);
		context.name = readUTF8(u + 2, c);
		context.desc = readUTF8(u + 4, c);
		u += 6;

		// reads the method attributes
		int code = 0;
		int exception = 0;
		String[] exceptions = null;
		String signature = null;
		int methodParameters = 0;
		int anns = 0;
		int ianns = 0;
		int tanns = 0;
		int itanns = 0;
		int dann = 0;
		int mpanns = 0;
		int impanns = 0;
		int firstAttribute = u;
		Attribute attributes = null;

		for (int i = readUnsignedShort(u); i > 0; --i) {
			String attrName = readUTF8(u + 2, c);
			// tests are sorted in decreasing frequency order
			// (based on frequencies observed on typical classes)
			if ("Code".equals(attrName)) {
				if ((context.flags & SKIP_CODE) == 0) {
					code = u + 8;
				}
			} else if ("Exceptions".equals(attrName)) {
				exceptions = new String[readUnsignedShort(u + 8)];
				exception = u + 10;
				for (int j = 0; j < exceptions.length; ++j) {
					exceptions[j] = readClass(exception, c);
					exception += 2;
				}
			} else if (SIGNATURES && "Signature".equals(attrName)) {
				signature = readUTF8(u + 8, c);
			} else if ("Deprecated".equals(attrName)) {
				context.access |= Opcodes.ACC_DEPRECATED;
			} else if (ANNOTATIONS
					&& "RuntimeVisibleAnnotations".equals(attrName)) {
				anns = u + 8;
			} else if (ANNOTATIONS
					&& "RuntimeVisibleTypeAnnotations".equals(attrName)) {
				tanns = u + 8;
			} else if (ANNOTATIONS && "AnnotationDefault".equals(attrName)) {
				dann = u + 8;
			} else if ("Synthetic".equals(attrName)) {
				context.access |= Opcodes.ACC_SYNTHETIC
						| ClassWriter.ACC_SYNTHETIC_ATTRIBUTE;
			} else if (ANNOTATIONS
					&& "RuntimeInvisibleAnnotations".equals(attrName)) {
				ianns = u + 8;
			} else if (ANNOTATIONS
					&& "RuntimeInvisibleTypeAnnotations".equals(attrName)) {
				itanns = u + 8;
			} else if (ANNOTATIONS
					&& "RuntimeVisibleParameterAnnotations".equals(attrName)) {
				mpanns = u + 8;
			} else if (ANNOTATIONS
					&& "RuntimeInvisibleParameterAnnotations".equals(attrName)) {
				impanns = u + 8;
			} else if ("MethodParameters".equals(attrName)) {
				methodParameters = u + 8;
			} else {
				Attribute attr = readAttribute(context.attrs, attrName, u + 8,
						readInt(u + 4), c, -1, null);
				if (attr != null) {
					attr.next = attributes;
					attributes = attr;
				}
			}
			u += 6 + readInt(u + 4);
		}
		u += 2;

		// visits the method declaration
		MethodVisitor mv = classVisitor.visitMethod(context.access,
				context.name, context.desc, signature, exceptions);
		if (mv == null) {
			return u;
		}

		/*
		 * if the returned MethodVisitor is in fact a MethodWriter, it means
		 * there is no method adapter between the reader and the writer. If, in
		 * addition, the writer's constant pool was copied from this reader
		 * (mw.cw.cr == this), and the signature and exceptions of the method
		 * have not been changed, then it is possible to skip all visit events
		 * and just copy the original code of the method to the writer (the
		 * access, name and descriptor can have been changed, this is not
		 * important since they are not copied as is from the reader).
		 */
		if (WRITER && mv instanceof MethodWriter) {
			MethodWriter mw = (MethodWriter) mv;
			if (mw.cw.cr == this &&
					(signature != null ? signature.equals(mw.signature) : mw.signature == null)) {
				boolean sameExceptions = false;
				if (exceptions == null) {
					sameExceptions = mw.exceptionCount == 0;
				} else if (exceptions.length == mw.exceptionCount) {
					sameExceptions = true;
					for (int j = exceptions.length - 1; j >= 0; --j) {
						exception -= 2;
						if (mw.exceptions[j] != readUnsignedShort(exception)) {
							sameExceptions = false;
							break;
						}
					}
				}
				if (sameExceptions) {
					/*
					 * we do not copy directly the code into MethodWriter to
					 * save a byte array copy operation. The real copy will be
					 * done in ClassWriter.toByteArray().
					 */
					mw.classReaderOffset = firstAttribute;
					mw.classReaderLength = u - firstAttribute;
					return u;
				}
			}
		}

		// visit the method parameters
		if (methodParameters != 0) {
			for (int i = b[methodParameters] & 0xFF, v = methodParameters + 1; i > 0; --i, v = v + 4) {
				mv.visitParameter(readUTF8(v, c), readUnsignedShort(v + 2));
			}
		}

		// visits the method annotations
		if (ANNOTATIONS && dann != 0) {
			AnnotationVisitor dv = mv.visitAnnotationDefault();
			readAnnotationValue(dann, c, null, dv);
			if (dv != null) {
				dv.visitEnd();
			}
		}
		if (ANNOTATIONS && anns != 0) {
			for (int i = readUnsignedShort(anns), v = anns + 2; i > 0; --i) {
				v = readAnnotationValues(v + 2, c, true,
						mv.visitAnnotation(readUTF8(v, c), true));
			}
		}
		if (ANNOTATIONS && ianns != 0) {
			for (int i = readUnsignedShort(ianns), v = ianns + 2; i > 0; --i) {
				v = readAnnotationValues(v + 2, c, true,
						mv.visitAnnotation(readUTF8(v, c), false));
			}
		}
		if (ANNOTATIONS && tanns != 0) {
			for (int i = readUnsignedShort(tanns), v = tanns + 2; i > 0; --i) {
				v = readAnnotationTarget(context, v);
				v = readAnnotationValues(v + 2, c, true,
						mv.visitTypeAnnotation(context.typeRef,
								context.typePath, readUTF8(v, c), true));
			}
		}
		if (ANNOTATIONS && itanns != 0) {
			for (int i = readUnsignedShort(itanns), v = itanns + 2; i > 0; --i) {
				v = readAnnotationTarget(context, v);
				v = readAnnotationValues(v + 2, c, true,
						mv.visitTypeAnnotation(context.typeRef,
								context.typePath, readUTF8(v, c), false));
			}
		}
		if (ANNOTATIONS && mpanns != 0) {
			readParameterAnnotations(mv, context, mpanns, true);
		}
		if (ANNOTATIONS && impanns != 0) {
			readParameterAnnotations(mv, context, impanns, false);
		}

		// visits the method attributes
		while (attributes != null) {
			Attribute attr = attributes.next;
			attributes.next = null;
			mv.visitAttribute(attributes);
			attributes = attr;
		}

		// visits the method code
		if (code != 0) {
			mv.visitCode();
			readCode(mv, context, code);
		}

		// visits the end of the method
		mv.visitEnd();

		return u;
	}

	/**
	 * Reads the bytecode of a method and makes the given visitor visit it.
	 *
	 * @param mv      the visitor that must visit the method's code.
	 * @param context information about the class being parsed.
	 * @param u       the start offset of the code attribute in the class file.
	 */
	private void readCode(final MethodVisitor mv, final Context context, int u) {
		// reads the header
		byte[] b = this.b;
		char[] c = context.buffer;
		int maxStack = readUnsignedShort(u);
		int maxLocals = readUnsignedShort(u + 2);
		int codeLength = readInt(u + 4);
		u += 8;

		// reads the bytecode to find the labels
		int codeStart = u;
		int codeEnd = u + codeLength;
		Label[] labels = context.labels = new Label[codeLength + 2];
		readLabel(codeLength + 1, labels);
		while (u < codeEnd) {
			int offset = u - codeStart;
			int opcode = b[u] & 0xFF;
			switch (ClassWriter.TYPE[opcode]) {
				case ClassWriter.NOARG_INSN:
				case ClassWriter.IMPLVAR_INSN:
					u += 1;
					break;
				case ClassWriter.LABEL_INSN:
					readLabel(offset + readShort(u + 1), labels);
					u += 3;
					break;
				case ClassWriter.ASM_LABEL_INSN:
					readLabel(offset + readUnsignedShort(u + 1), labels);
					u += 3;
					break;
				case ClassWriter.LABELW_INSN:
				case ClassWriter.ASM_LABELW_INSN:
					readLabel(offset + readInt(u + 1), labels);
					u += 5;
					break;
				case ClassWriter.WIDE_INSN:
					opcode = b[u + 1] & 0xFF;
					if (opcode == Opcodes.IINC) {
						u += 6;
					} else {
						u += 4;
					}
					break;
				case ClassWriter.TABL_INSN:
					// skips 0 to 3 padding bytes
					u = u + 4 - (offset & 3);
					// reads instruction
					readLabel(offset + readInt(u), labels);
					for (int i = readInt(u + 8) - readInt(u + 4) + 1; i > 0; --i) {
						readLabel(offset + readInt(u + 12), labels);
						u += 4;
					}
					u += 12;
					break;
				case ClassWriter.LOOK_INSN:
					// skips 0 to 3 padding bytes
					u = u + 4 - (offset & 3);
					// reads instruction
					readLabel(offset + readInt(u), labels);
					for (int i = readInt(u + 4); i > 0; --i) {
						readLabel(offset + readInt(u + 12), labels);
						u += 8;
					}
					u += 8;
					break;
				case ClassWriter.VAR_INSN:
				case ClassWriter.SBYTE_INSN:
				case ClassWriter.LDC_INSN:
					u += 2;
					break;
				case ClassWriter.SHORT_INSN:
				case ClassWriter.LDCW_INSN:
				case ClassWriter.FIELDORMETH_INSN:
				case ClassWriter.TYPE_INSN:
				case ClassWriter.IINC_INSN:
					u += 3;
					break;
				case ClassWriter.ITFMETH_INSN:
				case ClassWriter.INDYMETH_INSN:
					u += 5;
					break;
				// case MANA_INSN:
				default:
					u += 4;
					break;
			}
		}

		// reads the try catch entries to find the labels, and also visits them
		for (int i = readUnsignedShort(u); i > 0; --i) {
			Label start = readLabel(readUnsignedShort(u + 2), labels);
			Label end = readLabel(readUnsignedShort(u + 4), labels);
			Label handler = readLabel(readUnsignedShort(u + 6), labels);
			String type = readUTF8(items[readUnsignedShort(u + 8)], c);
			mv.visitTryCatchBlock(start, end, handler, type);
			u += 8;
		}
		u += 2;

		// reads the code attributes
		int[] tanns = null; // start index of each visible type annotation
		int[] itanns = null; // start index of each invisible type annotation
		int tann = 0; // current index in tanns array
		int itann = 0; // current index in itanns array
		int ntoff = -1; // next visible type annotation code offset
		int nitoff = -1; // next invisible type annotation code offset
		int varTable = 0;
		int varTypeTable = 0;
		boolean zip = true;
		boolean unzip = (context.flags & EXPAND_FRAMES) != 0;
		int stackMap = 0;
		int stackMapSize = 0;
		int frameCount = 0;
		Context frame = null;
		Attribute attributes = null;

		for (int i = readUnsignedShort(u); i > 0; --i) {
			String attrName = readUTF8(u + 2, c);
			if ("LocalVariableTable".equals(attrName)) {
				if ((context.flags & SKIP_DEBUG) == 0) {
					varTable = u + 8;
					for (int j = readUnsignedShort(u + 8), v = u; j > 0; --j) {
						int label = readUnsignedShort(v + 10);
						if (labels[label] == null) {
							readLabel(label, labels).status |= Label.DEBUG;
						}
						label += readUnsignedShort(v + 12);
						if (labels[label] == null) {
							readLabel(label, labels).status |= Label.DEBUG;
						}
						v += 10;
					}
				}
			} else if ("LocalVariableTypeTable".equals(attrName)) {
				varTypeTable = u + 8;
			} else if ("LineNumberTable".equals(attrName)) {
				if ((context.flags & SKIP_DEBUG) == 0) {
					for (int j = readUnsignedShort(u + 8), v = u; j > 0; --j) {
						int label = readUnsignedShort(v + 10);
						if (labels[label] == null) {
							readLabel(label, labels).status |= Label.DEBUG;
						}
						Label l = labels[label];
						while (l.line > 0) {
							if (l.next == null) {
								l.next = new Label();
							}
							l = l.next;
						}
						l.line = readUnsignedShort(v + 12);
						v += 4;
					}
				}
			} else if (ANNOTATIONS
					&& "RuntimeVisibleTypeAnnotations".equals(attrName)) {
				tanns = readTypeAnnotations(mv, context, u + 8, true);
				ntoff = tanns.length == 0 || readByte(tanns[0]) < 0x43 ? -1
						: readUnsignedShort(tanns[0] + 1);
			} else if (ANNOTATIONS
					&& "RuntimeInvisibleTypeAnnotations".equals(attrName)) {
				itanns = readTypeAnnotations(mv, context, u + 8, false);
				nitoff = itanns.length == 0 || readByte(itanns[0]) < 0x43 ? -1
						: readUnsignedShort(itanns[0] + 1);
			} else if (FRAMES && "StackMapTable".equals(attrName)) {
				if ((context.flags & SKIP_FRAMES) == 0) {
					stackMap = u + 10;
					stackMapSize = readInt(u + 4);
					frameCount = readUnsignedShort(u + 8);
				}
				/*
				 * here we do not extract the labels corresponding to the
				 * attribute content. This would require a full parsing of the
				 * attribute, which would need to be repeated in the second
				 * phase (see below). Instead the content of the attribute is
				 * read one frame at a time (i.e. after a frame has been
				 * visited, the next frame is read), and the labels it contains
				 * are also extracted one frame at a time. Thanks to the
				 * ordering of frames, having only a "one frame lookahead" is
				 * not a problem, i.e. it is not possible to see an offset
				 * smaller than the offset of the current insn and for which no
				 * Label exist.
				 */
				/*
				 * This is not true for UNINITIALIZED type offsets. We solve
				 * this by parsing the stack map table without a full decoding
				 * (see below).
				 */
			} else if (FRAMES && "StackMap".equals(attrName)) {
				if ((context.flags & SKIP_FRAMES) == 0) {
					zip = false;
					stackMap = u + 10;
					stackMapSize = readInt(u + 4);
					frameCount = readUnsignedShort(u + 8);
				}
				/*
				 * IMPORTANT! here we assume that the frames are ordered, as in
				 * the StackMapTable attribute, although this is not guaranteed
				 * by the attribute format.
				 */
			} else {
				for (int j = 0; j < context.attrs.length; ++j) {
					if (context.attrs[j].type.equals(attrName)) {
						Attribute attr = context.attrs[j].read(this, u + 8,
								readInt(u + 4), c, codeStart - 8, labels);
						if (attr != null) {
							attr.next = attributes;
							attributes = attr;
						}
					}
				}
			}
			u += 6 + readInt(u + 4);
		}
		u += 2;

		// generates the first (implicit) stack map frame
		if (FRAMES && stackMap != 0) {
			/*
			 * for the first explicit frame the offset is not offset_delta + 1
			 * but only offset_delta; setting the implicit frame offset to -1
			 * allow the use of the "offset_delta + 1" rule in all cases
			 */
			frame = context;
			frame.offset = -1;
			frame.mode = 0;
			frame.localCount = 0;
			frame.localDiff = 0;
			frame.stackCount = 0;
			frame.local = new Object[maxLocals];
			frame.stack = new Object[maxStack];
			if (unzip) {
				getImplicitFrame(context);
			}
			/*
			 * Finds labels for UNINITIALIZED frame types. Instead of decoding
			 * each element of the stack map table, we look for 3 consecutive
			 * bytes that "look like" an UNINITIALIZED type (tag 8, offset
			 * within code bounds, NEW instruction at this offset). We may find
			 * false positives (i.e. not real UNINITIALIZED types), but this
			 * should be rare, and the only consequence will be the creation of
			 * an unneeded label. This is better than creating a label for each
			 * NEW instruction, and faster than fully decoding the whole stack
			 * map table.
			 */
			for (int i = stackMap; i < stackMap + stackMapSize - 2; ++i) {
				if (b[i] == 8) { // UNINITIALIZED FRAME TYPE
					int v = readUnsignedShort(i + 1);
					if (v >= 0 && v < codeLength) {
						if ((b[codeStart + v] & 0xFF) == Opcodes.NEW) {
							readLabel(v, labels);
						}
					}
				}
			}
		}
		if ((context.flags & EXPAND_ASM_INSNS) != 0
				&& (context.flags & EXPAND_FRAMES) != 0) {
			// Expanding the ASM pseudo instructions can introduce F_INSERT
			// frames, even if the method does not currently have any frame.
			// Also these inserted frames must be computed by simulating the
			// effect of the bytecode instructions one by one, starting from the
			// first one and the last existing frame (or the implicit first
			// one). Finally, due to the way MethodWriter computes this (with
			// the compute = INSERTED_FRAMES option), MethodWriter needs to know
			// maxLocals before the first instruction is visited. For all these
			// reasons we always visit the implicit first frame in this case
			// (passing only maxLocals - the rest can be and is computed in
			// MethodWriter).
			mv.visitFrame(Opcodes.F_NEW, maxLocals, null, 0, null);
		}

		// visits the instructions
		int opcodeDelta = (context.flags & EXPAND_ASM_INSNS) == 0 ? -33 : 0;
		boolean insertFrame = false;
		u = codeStart;
		while (u < codeEnd) {
			int offset = u - codeStart;

			// visits the label and line number for this offset, if any
			Label l = labels[offset];
			if (l != null) {
				Label next = l.next;
				l.next = null;
				mv.visitLabel(l);
				if ((context.flags & SKIP_DEBUG) == 0 && l.line > 0) {
					mv.visitLineNumber(l.line, l);
					while (next != null) {
						mv.visitLineNumber(next.line, l);
						next = next.next;
					}
				}
			}

			// visits the frame for this offset, if any
			while (FRAMES && frame != null
					&& (frame.offset == offset || frame.offset == -1)) {
				// if there is a frame for this offset, makes the visitor visit
				// it, and reads the next frame if there is one.
				if (frame.offset != -1) {
					if (!zip || unzip) {
						mv.visitFrame(Opcodes.F_NEW, frame.localCount,
								frame.local, frame.stackCount, frame.stack);
					} else {
						mv.visitFrame(frame.mode, frame.localDiff, frame.local,
								frame.stackCount, frame.stack);
					}
					// if there is already a frame for this offset, there is no
					// need to insert a new one.
					insertFrame = false;
				}
				if (frameCount > 0) {
					stackMap = readFrame(stackMap, zip, unzip, frame);
					--frameCount;
				} else {
					frame = null;
				}
			}
			// inserts a frame for this offset, if requested by setting
			// insertFrame to true during the previous iteration. The actual
			// frame content will be computed in MethodWriter.
			if (FRAMES && insertFrame) {
				mv.visitFrame(ClassWriter.F_INSERT, 0, null, 0, null);
				insertFrame = false;
			}

			// visits the instruction at this offset
			int opcode = b[u] & 0xFF;
			switch (ClassWriter.TYPE[opcode]) {
				case ClassWriter.NOARG_INSN:
					mv.visitInsn(opcode);
					u += 1;
					break;
				case ClassWriter.IMPLVAR_INSN:
					if (opcode > Opcodes.ISTORE) {
						opcode -= 59; // ISTORE_0
						mv.visitVarInsn(Opcodes.ISTORE + (opcode >> 2),
								opcode & 0x3);
					} else {
						opcode -= 26; // ILOAD_0
						mv.visitVarInsn(Opcodes.ILOAD + (opcode >> 2), opcode & 0x3);
					}
					u += 1;
					break;
				case ClassWriter.LABEL_INSN:
					mv.visitJumpInsn(opcode, labels[offset + readShort(u + 1)]);
					u += 3;
					break;
				case ClassWriter.LABELW_INSN:
					mv.visitJumpInsn(opcode + opcodeDelta, labels[offset
							+ readInt(u + 1)]);
					u += 5;
					break;
				case ClassWriter.ASM_LABEL_INSN: {
					// changes temporary opcodes 202 to 217 (inclusive), 218
					// and 219 to IFEQ ... JSR (inclusive), IFNULL and
					// IFNONNULL
					opcode = opcode < 218 ? opcode - 49 : opcode - 20;
					Label target = labels[offset + readUnsignedShort(u + 1)];
					// replaces GOTO with GOTO_W, JSR with JSR_W and IFxxx
					// <l> with IFNOTxxx <L> GOTO_W <l> L:..., where IFNOTxxx is
					// the "opposite" opcode of IFxxx (i.e., IFNE for IFEQ)
					// and where <L> designates the instruction just after
					// the GOTO_W.
					if (opcode == Opcodes.GOTO || opcode == Opcodes.JSR) {
						mv.visitJumpInsn(opcode + 33, target);
					} else {
						opcode = opcode <= 166 ? ((opcode + 1) ^ 1) - 1
								: opcode ^ 1;
						Label endif = readLabel(offset + 3, labels);
						mv.visitJumpInsn(opcode, endif);
						mv.visitJumpInsn(200, target); // GOTO_W
						// endif designates the instruction just after GOTO_W,
						// and is visited as part of the next instruction. Since
						// it is a jump target, we need to insert a frame here.
						insertFrame = true;
					}
					u += 3;
					break;
				}
				case ClassWriter.ASM_LABELW_INSN: {
					// replaces the pseudo GOTO_W instruction with a real one.
					mv.visitJumpInsn(200, labels[offset + readInt(u + 1)]);
					// The instruction just after is a jump target (because pseudo
					// GOTO_W are used in patterns IFNOTxxx <L> GOTO_W <l> L:...,
					// see MethodWriter), so we need to insert a frame here.
					insertFrame = true;
					u += 5;
					break;
				}
				case ClassWriter.WIDE_INSN:
					opcode = b[u + 1] & 0xFF;
					if (opcode == Opcodes.IINC) {
						mv.visitIincInsn(readUnsignedShort(u + 2), readShort(u + 4));
						u += 6;
					} else {
						mv.visitVarInsn(opcode, readUnsignedShort(u + 2));
						u += 4;
					}
					break;
				case ClassWriter.TABL_INSN: {
					// skips 0 to 3 padding bytes
					u = u + 4 - (offset & 3);
					// reads instruction
					int label = offset + readInt(u);
					int min = readInt(u + 4);
					int max = readInt(u + 8);
					Label[] table = new Label[max - min + 1];
					u += 12;
					for (int i = 0; i < table.length; ++i) {
						table[i] = labels[offset + readInt(u)];
						u += 4;
					}
					mv.visitTableSwitchInsn(min, max, labels[label], table);
					break;
				}
				case ClassWriter.LOOK_INSN: {
					// skips 0 to 3 padding bytes
					u = u + 4 - (offset & 3);
					// reads instruction
					int label = offset + readInt(u);
					int len = readInt(u + 4);
					int[] keys = new int[len];
					Label[] values = new Label[len];
					u += 8;
					for (int i = 0; i < len; ++i) {
						keys[i] = readInt(u);
						values[i] = labels[offset + readInt(u + 4)];
						u += 8;
					}
					mv.visitLookupSwitchInsn(labels[label], keys, values);
					break;
				}
				case ClassWriter.VAR_INSN:
					mv.visitVarInsn(opcode, b[u + 1] & 0xFF);
					u += 2;
					break;
				case ClassWriter.SBYTE_INSN:
					mv.visitIntInsn(opcode, b[u + 1]);
					u += 2;
					break;
				case ClassWriter.SHORT_INSN:
					mv.visitIntInsn(opcode, readShort(u + 1));
					u += 3;
					break;
				case ClassWriter.LDC_INSN:
					mv.visitLdcInsn(readConst(b[u + 1] & 0xFF, c));
					u += 2;
					break;
				case ClassWriter.LDCW_INSN:
					mv.visitLdcInsn(readConst(readUnsignedShort(u + 1), c));
					u += 3;
					break;
				case ClassWriter.FIELDORMETH_INSN:
				case ClassWriter.ITFMETH_INSN: {
					int cpIndex = items[readUnsignedShort(u + 1)];
					boolean itf = b[cpIndex - 1] == ClassWriter.IMETH;
					String iowner = readClass(cpIndex, c);
					cpIndex = items[readUnsignedShort(cpIndex + 2)];
					String iname = readUTF8(cpIndex, c);
					String idesc = readUTF8(cpIndex + 2, c);
					if (opcode < Opcodes.INVOKEVIRTUAL) {
						mv.visitFieldInsn(opcode, iowner, iname, idesc);
					} else {
						mv.visitMethodInsn(opcode, iowner, iname, idesc, itf);
					}
					if (opcode == Opcodes.INVOKEINTERFACE) {
						u += 5;
					} else {
						u += 3;
					}
					break;
				}
				case ClassWriter.INDYMETH_INSN: {
					int cpIndex = items[readUnsignedShort(u + 1)];
					int bsmIndex = context.bootstrapMethods[readUnsignedShort(cpIndex)];
					Handle bsm = (Handle) readConst(readUnsignedShort(bsmIndex), c);
					int bsmArgCount = readUnsignedShort(bsmIndex + 2);
					Object[] bsmArgs = new Object[bsmArgCount];
					bsmIndex += 4;
					for (int i = 0; i < bsmArgCount; i++) {
						bsmArgs[i] = readConst(readUnsignedShort(bsmIndex), c);
						bsmIndex += 2;
					}
					cpIndex = items[readUnsignedShort(cpIndex + 2)];
					String iname = readUTF8(cpIndex, c);
					String idesc = readUTF8(cpIndex + 2, c);
					mv.visitInvokeDynamicInsn(iname, idesc, bsm, bsmArgs);
					u += 5;
					break;
				}
				case ClassWriter.TYPE_INSN:
					mv.visitTypeInsn(opcode, readClass(u + 1, c));
					u += 3;
					break;
				case ClassWriter.IINC_INSN:
					mv.visitIincInsn(b[u + 1] & 0xFF, b[u + 2]);
					u += 3;
					break;
				// case MANA_INSN:
				default:
					mv.visitMultiANewArrayInsn(readClass(u + 1, c), b[u + 3] & 0xFF);
					u += 4;
					break;
			}

			// visit the instruction annotations, if any
			while (tanns != null && tann < tanns.length && ntoff <= offset) {
				if (ntoff == offset) {
					int v = readAnnotationTarget(context, tanns[tann]);
					readAnnotationValues(v + 2, c, true,
							mv.visitInsnAnnotation(context.typeRef,
									context.typePath, readUTF8(v, c), true));
				}
				ntoff = ++tann >= tanns.length || readByte(tanns[tann]) < 0x43 ? -1
						: readUnsignedShort(tanns[tann] + 1);
			}
			while (itanns != null && itann < itanns.length && nitoff <= offset) {
				if (nitoff == offset) {
					int v = readAnnotationTarget(context, itanns[itann]);
					readAnnotationValues(v + 2, c, true,
							mv.visitInsnAnnotation(context.typeRef,
									context.typePath, readUTF8(v, c), false));
				}
				nitoff = ++itann >= itanns.length
						|| readByte(itanns[itann]) < 0x43 ? -1
						: readUnsignedShort(itanns[itann] + 1);
			}
		}
		if (labels[codeLength] != null) {
			mv.visitLabel(labels[codeLength]);
		}

		// visits the local variable tables
		if ((context.flags & SKIP_DEBUG) == 0 && varTable != 0) {
			int[] typeTable = null;
			if (varTypeTable != 0) {
				u = varTypeTable + 2;
				typeTable = new int[readUnsignedShort(varTypeTable) * 3];
				for (int i = typeTable.length; i > 0; ) {
					typeTable[--i] = u + 6; // signature
					typeTable[--i] = readUnsignedShort(u + 8); // index
					typeTable[--i] = readUnsignedShort(u); // start
					u += 10;
				}
			}
			u = varTable + 2;
			for (int i = readUnsignedShort(varTable); i > 0; --i) {
				int start = readUnsignedShort(u);
				int length = readUnsignedShort(u + 2);
				int index = readUnsignedShort(u + 8);
				String vsignature = null;
				if (typeTable != null) {
					for (int j = 0; j < typeTable.length; j += 3) {
						if (typeTable[j] == start && typeTable[j + 1] == index) {
							vsignature = readUTF8(typeTable[j + 2], c);
							break;
						}
					}
				}
				mv.visitLocalVariable(readUTF8(u + 4, c), readUTF8(u + 6, c),
						vsignature, labels[start], labels[start + length],
						index);
				u += 10;
			}
		}

		// visits the local variables type annotations
		if (tanns != null) {
			for (int i = 0; i < tanns.length; ++i) {
				if ((readByte(tanns[i]) >> 1) == (0x40 >> 1)) {
					int v = readAnnotationTarget(context, tanns[i]);
					v = readAnnotationValues(v + 2, c, true,
							mv.visitLocalVariableAnnotation(context.typeRef,
									context.typePath, context.start,
									context.end, context.index, readUTF8(v, c),
									true));
				}
			}
		}
		if (itanns != null) {
			for (int i = 0; i < itanns.length; ++i) {
				if ((readByte(itanns[i]) >> 1) == (0x40 >> 1)) {
					int v = readAnnotationTarget(context, itanns[i]);
					v = readAnnotationValues(v + 2, c, true,
							mv.visitLocalVariableAnnotation(context.typeRef,
									context.typePath, context.start,
									context.end, context.index, readUTF8(v, c),
									false));
				}
			}
		}

		// visits the code attributes
		while (attributes != null) {
			Attribute attr = attributes.next;
			attributes.next = null;
			mv.visitAttribute(attributes);
			attributes = attr;
		}

		// visits the max stack and max locals values
		mv.visitMaxs(maxStack, maxLocals);
	}

	/**
	 * Parses a type annotation table to find the labels, and to visit the try
	 * catch block annotations.
	 *
	 * @param u       the start offset of a type annotation table.
	 * @param mv      the method visitor to be used to visit the try catch block
	 *                annotations.
	 * @param context information about the class being parsed.
	 * @param visible if the type annotation table to parse contains runtime visible
	 *                annotations.
	 * @return the start offset of each type annotation in the parsed table.
	 */
	private int[] readTypeAnnotations(final MethodVisitor mv,
									  final Context context, int u, boolean visible) {
		char[] c = context.buffer;
		int[] offsets = new int[readUnsignedShort(u)];
		u += 2;
		for (int i = 0; i < offsets.length; ++i) {
			offsets[i] = u;
			int target = readInt(u);
			switch (target >>> 24) {
				case 0x00: // CLASS_TYPE_PARAMETER
				case 0x01: // METHOD_TYPE_PARAMETER
				case 0x16: // METHOD_FORMAL_PARAMETER
					u += 2;
					break;
				case 0x13: // FIELD
				case 0x14: // METHOD_RETURN
				case 0x15: // METHOD_RECEIVER
					u += 1;
					break;
				case 0x40: // LOCAL_VARIABLE
				case 0x41: // RESOURCE_VARIABLE
					for (int j = readUnsignedShort(u + 1); j > 0; --j) {
						int start = readUnsignedShort(u + 3);
						int length = readUnsignedShort(u + 5);
						readLabel(start, context.labels);
						readLabel(start + length, context.labels);
						u += 6;
					}
					u += 3;
					break;
				case 0x47: // CAST
				case 0x48: // CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT
				case 0x49: // METHOD_INVOCATION_TYPE_ARGUMENT
				case 0x4A: // CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT
				case 0x4B: // METHOD_REFERENCE_TYPE_ARGUMENT
					u += 4;
					break;
				// case 0x10: // CLASS_EXTENDS
				// case 0x11: // CLASS_TYPE_PARAMETER_BOUND
				// case 0x12: // METHOD_TYPE_PARAMETER_BOUND
				// case 0x17: // THROWS
				// case 0x42: // EXCEPTION_PARAMETER
				// case 0x43: // INSTANCEOF
				// case 0x44: // NEW
				// case 0x45: // CONSTRUCTOR_REFERENCE
				// case 0x46: // METHOD_REFERENCE
				default:
					u += 3;
					break;
			}
			int pathLength = readByte(u);
			if ((target >>> 24) == 0x42) {
				TypePath path = pathLength == 0 ? null : new TypePath(b, u);
				u += 1 + 2 * pathLength;
				u = readAnnotationValues(u + 2, c, true,
						mv.visitTryCatchAnnotation(target, path,
								readUTF8(u, c), visible));
			} else {
				u = readAnnotationValues(u + 3 + 2 * pathLength, c, true, null);
			}
		}
		return offsets;
	}

	/**
	 * Parses the header of a type annotation to extract its target_type and
	 * target_path (the result is stored in the given context), and returns the
	 * start offset of the rest of the type_annotation structure (i.e. the
	 * offset to the type_index field, which is followed by
	 * num_element_value_pairs and then the name,value pairs).
	 *
	 * @param context information about the class being parsed. This is where the
	 *                extracted target_type and target_path must be stored.
	 * @param u       the start offset of a type_annotation structure.
	 * @return the start offset of the rest of the type_annotation structure.
	 */
	private int readAnnotationTarget(final Context context, int u) {
		int target = readInt(u);
		switch (target >>> 24) {
			case 0x00: // CLASS_TYPE_PARAMETER
			case 0x01: // METHOD_TYPE_PARAMETER
			case 0x16: // METHOD_FORMAL_PARAMETER
				target &= 0xFFFF0000;
				u += 2;
				break;
			case 0x13: // FIELD
			case 0x14: // METHOD_RETURN
			case 0x15: // METHOD_RECEIVER
				target &= 0xFF000000;
				u += 1;
				break;
			case 0x40: // LOCAL_VARIABLE
			case 0x41: { // RESOURCE_VARIABLE
				target &= 0xFF000000;
				int n = readUnsignedShort(u + 1);
				context.start = new Label[n];
				context.end = new Label[n];
				context.index = new int[n];
				u += 3;
				for (int i = 0; i < n; ++i) {
					int start = readUnsignedShort(u);
					int length = readUnsignedShort(u + 2);
					context.start[i] = readLabel(start, context.labels);
					context.end[i] = readLabel(start + length, context.labels);
					context.index[i] = readUnsignedShort(u + 4);
					u += 6;
				}
				break;
			}
			case 0x47: // CAST
			case 0x48: // CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT
			case 0x49: // METHOD_INVOCATION_TYPE_ARGUMENT
			case 0x4A: // CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT
			case 0x4B: // METHOD_REFERENCE_TYPE_ARGUMENT
				target &= 0xFF0000FF;
				u += 4;
				break;
			// case 0x10: // CLASS_EXTENDS
			// case 0x11: // CLASS_TYPE_PARAMETER_BOUND
			// case 0x12: // METHOD_TYPE_PARAMETER_BOUND
			// case 0x17: // THROWS
			// case 0x42: // EXCEPTION_PARAMETER
			// case 0x43: // INSTANCEOF
			// case 0x44: // NEW
			// case 0x45: // CONSTRUCTOR_REFERENCE
			// case 0x46: // METHOD_REFERENCE
			default:
				target &= (target >>> 24) < 0x43 ? 0xFFFFFF00 : 0xFF000000;
				u += 3;
				break;
		}
		int pathLength = readByte(u);
		context.typeRef = target;
		context.typePath = pathLength == 0 ? null : new TypePath(b, u);
		return u + 1 + 2 * pathLength;
	}

	/**
	 * Reads parameter annotations and makes the given visitor visit them.
	 *
	 * @param mv      the visitor that must visit the annotations.
	 * @param context information about the class being parsed.
	 * @param v       start offset in {@link #b b} of the annotations to be read.
	 * @param visible <tt>true</tt> if the annotations to be read are visible at
	 *                runtime.
	 */
	private void readParameterAnnotations(final MethodVisitor mv,
										  final Context context, int v, final boolean visible) {
		int i;
		int n = b[v++] & 0xFF;
		// workaround for a bug in javac (javac compiler generates a parameter
		// annotation array whose size is equal to the number of parameters in
		// the Java source file, while it should generate an array whose size is
		// equal to the number of parameters in the method descriptor - which
		// includes the synthetic parameters added by the compiler). This work-
		// around supposes that the synthetic parameters are the first ones.
		int synthetics = Type.getArgumentTypes(context.desc).length - n;
		AnnotationVisitor av;
		for (i = 0; i < synthetics; ++i) {
			// virtual annotation to detect synthetic parameters in MethodWriter
			av = mv.visitParameterAnnotation(i, "Ljava/lang/Synthetic;", false);
			if (av != null) {
				av.visitEnd();
			}
		}
		char[] c = context.buffer;
		for (; i < n + synthetics; ++i) {
			int j = readUnsignedShort(v);
			v += 2;
			for (; j > 0; --j) {
				av = mv.visitParameterAnnotation(i, readUTF8(v, c), visible);
				v = readAnnotationValues(v + 2, c, true, av);
			}
		}
	}

	/**
	 * Reads the values of an annotation and makes the given visitor visit them.
	 *
	 * @param v     the start offset in {@link #b b} of the values to be read
	 *              (including the unsigned short that gives the number of
	 *              values).
	 * @param buf   buffer to be used to call {@link #readUTF8 readUTF8},
	 *              {@link #readClass(int, char[]) readClass} or {@link #readConst
	 *              readConst}.
	 * @param named if the annotation values are named or not.
	 * @param av    the visitor that must visit the values.
	 * @return the end offset of the annotation values.
	 */
	private int readAnnotationValues(int v, final char[] buf,
									 final boolean named, final AnnotationVisitor av) {
		int i = readUnsignedShort(v);
		v += 2;
		if (named) {
			for (; i > 0; --i) {
				v = readAnnotationValue(v + 2, buf, readUTF8(v, buf), av);
			}
		} else {
			for (; i > 0; --i) {
				v = readAnnotationValue(v, buf, null, av);
			}
		}
		if (av != null) {
			av.visitEnd();
		}
		return v;
	}

	/**
	 * Reads a value of an annotation and makes the given visitor visit it.
	 *
	 * @param v    the start offset in {@link #b b} of the value to be read
	 *             (<i>not including the value name constant pool index</i>).
	 * @param buf  buffer to be used to call {@link #readUTF8 readUTF8},
	 *             {@link #readClass(int, char[]) readClass} or {@link #readConst
	 *             readConst}.
	 * @param name the name of the value to be read.
	 * @param av   the visitor that must visit the value.
	 * @return the end offset of the annotation value.
	 */
	private int readAnnotationValue(int v, final char[] buf, final String name,
									final AnnotationVisitor av) {
		int i;
		if (av == null) {
			switch (b[v] & 0xFF) {
				case 'e': // enum_const_value
					return v + 5;
				case '@': // annotation_value
					return readAnnotationValues(v + 3, buf, true, null);
				case '[': // array_value
					return readAnnotationValues(v + 1, buf, false, null);
				default:
					return v + 3;
			}
		}
		switch (b[v++] & 0xFF) {
			case 'I': // pointer to CONSTANT_Integer
			case 'J': // pointer to CONSTANT_Long
			case 'F': // pointer to CONSTANT_Float
			case 'D': // pointer to CONSTANT_Double
				av.visit(name, readConst(readUnsignedShort(v), buf));
				v += 2;
				break;
			case 'B': // pointer to CONSTANT_Byte
				av.visit(name, (byte) readInt(items[readUnsignedShort(v)]));
				v += 2;
				break;
			case 'Z': // pointer to CONSTANT_Boolean
				av.visit(name,
						readInt(items[readUnsignedShort(v)]) == 0 ? Boolean.FALSE
								: Boolean.TRUE);
				v += 2;
				break;
			case 'S': // pointer to CONSTANT_Short
				av.visit(name, (short) readInt(items[readUnsignedShort(v)]));
				v += 2;
				break;
			case 'C': // pointer to CONSTANT_Char
				av.visit(name, (char) readInt(items[readUnsignedShort(v)]));
				v += 2;
				break;
			case 's': // pointer to CONSTANT_Utf8
				av.visit(name, readUTF8(v, buf));
				v += 2;
				break;
			case 'e': // enum_const_value
				av.visitEnum(name, readUTF8(v, buf), readUTF8(v + 2, buf));
				v += 4;
				break;
			case 'c': // class_info
				av.visit(name, Type.getType(readUTF8(v, buf)));
				v += 2;
				break;
			case '@': // annotation_value
				v = readAnnotationValues(v + 2, buf, true,
						av.visitAnnotation(name, readUTF8(v, buf)));
				break;
			case '[': // array_value
				int size = readUnsignedShort(v);
				v += 2;
				if (size == 0) {
					return readAnnotationValues(v - 2, buf, false,
							av.visitArray(name));
				}
				switch (this.b[v++] & 0xFF) {
					case 'B':
						byte[] bv = new byte[size];
						for (i = 0; i < size; i++) {
							bv[i] = (byte) readInt(items[readUnsignedShort(v)]);
							v += 3;
						}
						av.visit(name, bv);
						--v;
						break;
					case 'Z':
						boolean[] zv = new boolean[size];
						for (i = 0; i < size; i++) {
							zv[i] = readInt(items[readUnsignedShort(v)]) != 0;
							v += 3;
						}
						av.visit(name, zv);
						--v;
						break;
					case 'S':
						short[] sv = new short[size];
						for (i = 0; i < size; i++) {
							sv[i] = (short) readInt(items[readUnsignedShort(v)]);
							v += 3;
						}
						av.visit(name, sv);
						--v;
						break;
					case 'C':
						char[] cv = new char[size];
						for (i = 0; i < size; i++) {
							cv[i] = (char) readInt(items[readUnsignedShort(v)]);
							v += 3;
						}
						av.visit(name, cv);
						--v;
						break;
					case 'I':
						int[] iv = new int[size];
						for (i = 0; i < size; i++) {
							iv[i] = readInt(items[readUnsignedShort(v)]);
							v += 3;
						}
						av.visit(name, iv);
						--v;
						break;
					case 'J':
						long[] lv = new long[size];
						for (i = 0; i < size; i++) {
							lv[i] = readLong(items[readUnsignedShort(v)]);
							v += 3;
						}
						av.visit(name, lv);
						--v;
						break;
					case 'F':
						float[] fv = new float[size];
						for (i = 0; i < size; i++) {
							fv[i] = Float
									.intBitsToFloat(readInt(items[readUnsignedShort(v)]));
							v += 3;
						}
						av.visit(name, fv);
						--v;
						break;
					case 'D':
						double[] dv = new double[size];
						for (i = 0; i < size; i++) {
							dv[i] = Double
									.longBitsToDouble(readLong(items[readUnsignedShort(v)]));
							v += 3;
						}
						av.visit(name, dv);
						--v;
						break;
					default:
						v = readAnnotationValues(v - 3, buf, false, av.visitArray(name));
				}
		}
		return v;
	}

	/**
	 * Computes the implicit frame of the method currently being parsed (as
	 * defined in the given {@link Context}) and stores it in the given context.
	 *
	 * @param frame information about the class being parsed.
	 */
	private void getImplicitFrame(final Context frame) {
		String desc = frame.desc;
		Object[] locals = frame.local;
		int local = 0;
		if ((frame.access & Opcodes.ACC_STATIC) == 0) {
			if ("<init>".equals(frame.name)) {
				locals[local++] = Opcodes.UNINITIALIZED_THIS;
			} else {
				locals[local++] = readClass(header + 2, frame.buffer);
			}
		}
		int i = 1;
		loop:
		while (true) {
			int j = i;
			switch (desc.charAt(i++)) {
				case 'Z':
				case 'C':
				case 'B':
				case 'S':
				case 'I':
					locals[local++] = Opcodes.INTEGER;
					break;
				case 'F':
					locals[local++] = Opcodes.FLOAT;
					break;
				case 'J':
					locals[local++] = Opcodes.LONG;
					break;
				case 'D':
					locals[local++] = Opcodes.DOUBLE;
					break;
				case '[':
					while (desc.charAt(i) == '[') {
						++i;
					}
					if (desc.charAt(i) == 'L') {
						++i;
						while (desc.charAt(i) != ';') {
							++i;
						}
					}
					locals[local++] = desc.substring(j, ++i);
					break;
				case 'L':
					while (desc.charAt(i) != ';') {
						++i;
					}
					locals[local++] = desc.substring(j + 1, i++);
					break;
				default:
					break loop;
			}
		}
		frame.localCount = local;
	}

	/**
	 * Reads a stack map frame and stores the result in the given
	 * {@link Context} object.
	 *
	 * @param stackMap the start offset of a stack map frame in the class file.
	 * @param zip      if the stack map frame at stackMap is compressed or not.
	 * @param unzip    if the stack map frame must be uncompressed.
	 * @param frame    where the parsed stack map frame must be stored.
	 * @return the offset of the first byte following the parsed frame.
	 */
	private int readFrame(int stackMap, boolean zip, boolean unzip,
						  Context frame) {
		char[] c = frame.buffer;
		Label[] labels = frame.labels;
		int tag;
		int delta;
		if (zip) {
			tag = b[stackMap++] & 0xFF;
		} else {
			tag = MethodWriter.FULL_FRAME;
			frame.offset = -1;
		}
		frame.localDiff = 0;
		if (tag < MethodWriter.SAME_LOCALS_1_STACK_ITEM_FRAME) {
			delta = tag;
			frame.mode = Opcodes.F_SAME;
			frame.stackCount = 0;
		} else if (tag < MethodWriter.RESERVED) {
			delta = tag - MethodWriter.SAME_LOCALS_1_STACK_ITEM_FRAME;
			stackMap = readFrameType(frame.stack, 0, stackMap, c, labels);
			frame.mode = Opcodes.F_SAME1;
			frame.stackCount = 1;
		} else {
			delta = readUnsignedShort(stackMap);
			stackMap += 2;
			if (tag == MethodWriter.SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED) {
				stackMap = readFrameType(frame.stack, 0, stackMap, c, labels);
				frame.mode = Opcodes.F_SAME1;
				frame.stackCount = 1;
			} else if (tag >= MethodWriter.CHOP_FRAME
					&& tag < MethodWriter.SAME_FRAME_EXTENDED) {
				frame.mode = Opcodes.F_CHOP;
				frame.localDiff = MethodWriter.SAME_FRAME_EXTENDED - tag;
				frame.localCount -= frame.localDiff;
				frame.stackCount = 0;
			} else if (tag == MethodWriter.SAME_FRAME_EXTENDED) {
				frame.mode = Opcodes.F_SAME;
				frame.stackCount = 0;
			} else if (tag < MethodWriter.FULL_FRAME) {
				int local = unzip ? frame.localCount : 0;
				for (int i = tag - MethodWriter.SAME_FRAME_EXTENDED; i > 0; i--) {
					stackMap = readFrameType(frame.local, local++, stackMap, c,
							labels);
				}
				frame.mode = Opcodes.F_APPEND;
				frame.localDiff = tag - MethodWriter.SAME_FRAME_EXTENDED;
				frame.localCount += frame.localDiff;
				frame.stackCount = 0;
			} else { // if (tag == FULL_FRAME) {
				frame.mode = Opcodes.F_FULL;
				int n = readUnsignedShort(stackMap);
				stackMap += 2;
				frame.localDiff = n;
				frame.localCount = n;
				for (int local = 0; n > 0; n--) {
					stackMap = readFrameType(frame.local, local++, stackMap, c,
							labels);
				}
				n = readUnsignedShort(stackMap);
				stackMap += 2;
				frame.stackCount = n;
				for (int stack = 0; n > 0; n--) {
					stackMap = readFrameType(frame.stack, stack++, stackMap, c,
							labels);
				}
			}
		}
		frame.offset += delta + 1;
		readLabel(frame.offset, labels);
		return stackMap;
	}

	/**
	 * Reads a stack map frame type and stores it at the given index in the
	 * given array.
	 *
	 * @param frame  the array where the parsed type must be stored.
	 * @param index  the index in 'frame' where the parsed type must be stored.
	 * @param v      the start offset of the stack map frame type to read.
	 * @param buf    a buffer to read strings.
	 * @param labels the labels of the method currently being parsed, indexed by
	 *               their offset. If the parsed type is an Uninitialized type, a
	 *               new label for the corresponding NEW instruction is stored in
	 *               this array if it does not already exist.
	 * @return the offset of the first byte after the parsed type.
	 */
	private int readFrameType(final Object[] frame, final int index, int v,
							  final char[] buf, final Label[] labels) {
		int type = b[v++] & 0xFF;
		switch (type) {
			case 0:
				frame[index] = Opcodes.TOP;
				break;
			case 1:
				frame[index] = Opcodes.INTEGER;
				break;
			case 2:
				frame[index] = Opcodes.FLOAT;
				break;
			case 3:
				frame[index] = Opcodes.DOUBLE;
				break;
			case 4:
				frame[index] = Opcodes.LONG;
				break;
			case 5:
				frame[index] = Opcodes.NULL;
				break;
			case 6:
				frame[index] = Opcodes.UNINITIALIZED_THIS;
				break;
			case 7: // Object
				frame[index] = readClass(v, buf);
				v += 2;
				break;
			default: // Uninitialized
				frame[index] = readLabel(readUnsignedShort(v), labels);
				v += 2;
		}
		return v;
	}

	/**
	 * Returns the label corresponding to the given offset. The default
	 * implementation of this method creates a label for the given offset if it
	 * has not been already created.
	 *
	 * @param offset a bytecode offset in a method.
	 * @param labels the already created labels, indexed by their offset. If a
	 *               label already exists for offset this method must not create a
	 *               new one. Otherwise it must store the new label in this array.
	 * @return a non null Label, which must be equal to labels[offset].
	 */
	protected Label readLabel(int offset, Label[] labels) {
		// SPRING PATCH: leniently handle offset mismatch
		if (offset >= labels.length) {
			return new Label();
		}
		// END OF PATCH
		if (labels[offset] == null) {
			labels[offset] = new Label();
		}
		return labels[offset];
	}

	/**
	 * Returns the start index of the attribute_info structure of this class.
	 *
	 * @return the start index of the attribute_info structure of this class.
	 */
	private int getAttributes() {
		// skips the header
		int u = header + 8 + readUnsignedShort(header + 6) * 2;
		// skips fields and methods
		for (int i = readUnsignedShort(u); i > 0; --i) {
			for (int j = readUnsignedShort(u + 8); j > 0; --j) {
				u += 6 + readInt(u + 12);
			}
			u += 8;
		}
		u += 2;
		for (int i = readUnsignedShort(u); i > 0; --i) {
			for (int j = readUnsignedShort(u + 8); j > 0; --j) {
				u += 6 + readInt(u + 12);
			}
			u += 8;
		}
		// the attribute_info structure starts just after the methods
		return u + 2;
	}

	/**
	 * Reads an attribute in {@link #b b}.
	 *
	 * @param attrs   prototypes of the attributes that must be parsed during the
	 *                visit of the class. Any attribute whose type is not equal to
	 *                the type of one the prototypes is ignored (i.e. an empty
	 *                {@link Attribute} instance is returned).
	 * @param type    the type of the attribute.
	 * @param off     index of the first byte of the attribute's content in
	 *                {@link #b b}. The 6 attribute header bytes, containing the
	 *                type and the length of the attribute, are not taken into
	 *                account here (they have already been read).
	 * @param len     the length of the attribute's content.
	 * @param buf     buffer to be used to call {@link #readUTF8 readUTF8},
	 *                {@link #readClass(int, char[]) readClass} or {@link #readConst
	 *                readConst}.
	 * @param codeOff index of the first byte of code's attribute content in
	 *                {@link #b b}, or -1 if the attribute to be read is not a code
	 *                attribute. The 6 attribute header bytes, containing the type
	 *                and the length of the attribute, are not taken into account
	 *                here.
	 * @param labels  the labels of the method's code, or <tt>null</tt> if the
	 *                attribute to be read is not a code attribute.
	 * @return the attribute that has been read, or <tt>null</tt> to skip this
	 * attribute.
	 */
	private Attribute readAttribute(final Attribute[] attrs, final String type,
									final int off, final int len, final char[] buf, final int codeOff,
									final Label[] labels) {
		for (int i = 0; i < attrs.length; ++i) {
			if (attrs[i].type.equals(type)) {
				return attrs[i].read(this, off, len, buf, codeOff, labels);
			}
		}
		return new Attribute(type).read(this, off, len, null, -1, null);
	}

	// ------------------------------------------------------------------------
	// Utility methods: low level parsing
	// ------------------------------------------------------------------------

	/**
	 * Returns the number of constant pool items in {@link #b b}.
	 *
	 * @return the number of constant pool items in {@link #b b}.
	 */
	public int getItemCount() {
		return items.length;
	}

	/**
	 * Returns the start index of the constant pool item in {@link #b b}, plus
	 * one. <i>This method is intended for {@link Attribute} sub classes, and is
	 * normally not needed by class generators or adapters.</i>
	 *
	 * @param item the index a constant pool item.
	 * @return the start index of the constant pool item in {@link #b b}, plus
	 * one.
	 */
	public int getItem(final int item) {
		return items[item];
	}

	/**
	 * Returns the maximum length of the strings contained in the constant pool
	 * of the class.
	 *
	 * @return the maximum length of the strings contained in the constant pool
	 * of the class.
	 */
	public int getMaxStringLength() {
		return maxStringLength;
	}

	/**
	 * Reads a byte value in {@link #b b}. <i>This method is intended for
	 * {@link Attribute} sub classes, and is normally not needed by class
	 * generators or adapters.</i>
	 *
	 * @param index the start index of the value to be read in {@link #b b}.
	 * @return the read value.
	 */
	public int readByte(final int index) {
		return b[index] & 0xFF;
	}

	/**
	 * Reads an unsigned short value in {@link #b b}. <i>This method is intended
	 * for {@link Attribute} sub classes, and is normally not needed by class
	 * generators or adapters.</i>
	 *
	 * @param index the start index of the value to be read in {@link #b b}.
	 * @return the read value.
	 */
	public int readUnsignedShort(final int index) {
		byte[] b = this.b;
		return ((b[index] & 0xFF) << 8) | (b[index + 1] & 0xFF);
	}

	/**
	 * Reads a signed short value in {@link #b b}. <i>This method is intended
	 * for {@link Attribute} sub classes, and is normally not needed by class
	 * generators or adapters.</i>
	 *
	 * @param index the start index of the value to be read in {@link #b b}.
	 * @return the read value.
	 */
	public short readShort(final int index) {
		byte[] b = this.b;
		return (short) (((b[index] & 0xFF) << 8) | (b[index + 1] & 0xFF));
	}

	/**
	 * Reads a signed int value in {@link #b b}. <i>This method is intended for
	 * {@link Attribute} sub classes, and is normally not needed by class
	 * generators or adapters.</i>
	 *
	 * @param index the start index of the value to be read in {@link #b b}.
	 * @return the read value.
	 */
	public int readInt(final int index) {
		byte[] b = this.b;
		return ((b[index] & 0xFF) << 24) | ((b[index + 1] & 0xFF) << 16)
				| ((b[index + 2] & 0xFF) << 8) | (b[index + 3] & 0xFF);
	}

	/**
	 * Reads a signed long value in {@link #b b}. <i>This method is intended for
	 * {@link Attribute} sub classes, and is normally not needed by class
	 * generators or adapters.</i>
	 *
	 * @param index the start index of the value to be read in {@link #b b}.
	 * @return the read value.
	 */
	public long readLong(final int index) {
		long l1 = readInt(index);
		long l0 = readInt(index + 4) & 0xFFFFFFFFL;
		return (l1 << 32) | l0;
	}

	/**
	 * Reads an UTF8 string constant pool item in {@link #b b}. <i>This method
	 * is intended for {@link Attribute} sub classes, and is normally not needed
	 * by class generators or adapters.</i>
	 *
	 * @param index the start index of an unsigned short value in {@link #b b},
	 *              whose value is the index of an UTF8 constant pool item.
	 * @param buf   buffer to be used to read the item. This buffer must be
	 *              sufficiently large. It is not automatically resized.
	 * @return the String corresponding to the specified UTF8 item.
	 */
	public String readUTF8(int index, final char[] buf) {
		int item = readUnsignedShort(index);
		if (index == 0 || item == 0) {
			return null;
		}
		String s = strings[item];
		if (s != null) {
			return s;
		}
		index = items[item];
		return strings[item] = readUTF(index + 2, readUnsignedShort(index), buf);
	}

	/**
	 * Reads UTF8 string in {@link #b b}.
	 *
	 * @param index  start offset of the UTF8 string to be read.
	 * @param utfLen length of the UTF8 string to be read.
	 * @param buf    buffer to be used to read the string. This buffer must be
	 *               sufficiently large. It is not automatically resized.
	 * @return the String corresponding to the specified UTF8 string.
	 */
	private String readUTF(int index, final int utfLen, final char[] buf) {
		int endIndex = index + utfLen;
		byte[] b = this.b;
		int strLen = 0;
		int c;
		int st = 0;
		char cc = 0;
		while (index < endIndex) {
			c = b[index++];
			switch (st) {
				case 0:
					c = c & 0xFF;
					if (c < 0x80) { // 0xxxxxxx
						buf[strLen++] = (char) c;
					} else if (c < 0xE0 && c > 0xBF) { // 110x xxxx 10xx xxxx
						cc = (char) (c & 0x1F);
						st = 1;
					} else { // 1110 xxxx 10xx xxxx 10xx xxxx
						cc = (char) (c & 0x0F);
						st = 2;
					}
					break;

				case 1: // byte 2 of 2-byte char or byte 3 of 3-byte char
					buf[strLen++] = (char) ((cc << 6) | (c & 0x3F));
					st = 0;
					break;

				case 2: // byte 2 of 3-byte char
					cc = (char) ((cc << 6) | (c & 0x3F));
					st = 1;
					break;
			}
		}
		return new String(buf, 0, strLen);
	}

	/**
	 * Read a stringish constant item (CONSTANT_Class, CONSTANT_String,
	 * CONSTANT_MethodType, CONSTANT_Module or CONSTANT_Package
	 *
	 * @param index
	 * @param buf
	 * @return
	 */
	private String readStringish(final int index, final char[] buf) {
		// computes the start index of the item in b
		// and reads the CONSTANT_Utf8 item designated by
		// the first two bytes of this item
		return readUTF8(items[readUnsignedShort(index)], buf);
	}

	/**
	 * Reads a class constant pool item in {@link #b b}. <i>This method is
	 * intended for {@link Attribute} sub classes, and is normally not needed by
	 * class generators or adapters.</i>
	 *
	 * @param index the start index of an unsigned short value in {@link #b b},
	 *              whose value is the index of a class constant pool item.
	 * @param buf   buffer to be used to read the item. This buffer must be
	 *              sufficiently large. It is not automatically resized.
	 * @return the String corresponding to the specified class item.
	 */
	public String readClass(final int index, final char[] buf) {
		return readStringish(index, buf);
	}

	/**
	 * Reads a module constant pool item in {@link #b b}. <i>This method is
	 * intended for {@link Attribute} sub classes, and is normally not needed by
	 * class generators or adapters.</i>
	 *
	 * @param index the start index of an unsigned short value in {@link #b b},
	 *              whose value is the index of a module constant pool item.
	 * @param buf   buffer to be used to read the item. This buffer must be
	 *              sufficiently large. It is not automatically resized.
	 * @return the String corresponding to the specified module item.
	 */
	public String readModule(final int index, final char[] buf) {
		return readStringish(index, buf);
	}

	/**
	 * Reads a module constant pool item in {@link #b b}. <i>This method is
	 * intended for {@link Attribute} sub classes, and is normally not needed by
	 * class generators or adapters.</i>
	 *
	 * @param index the start index of an unsigned short value in {@link #b b},
	 *              whose value is the index of a module constant pool item.
	 * @param buf   buffer to be used to read the item. This buffer must be
	 *              sufficiently large. It is not automatically resized.
	 * @return the String corresponding to the specified module item.
	 */
	public String readPackage(final int index, final char[] buf) {
		return readStringish(index, buf);
	}

	/**
	 * Reads a numeric or string constant pool item in {@link #b b}. <i>This
	 * method is intended for {@link Attribute} sub classes, and is normally not
	 * needed by class generators or adapters.</i>
	 *
	 * @param item the index of a constant pool item.
	 * @param buf  buffer to be used to read the item. This buffer must be
	 *             sufficiently large. It is not automatically resized.
	 * @return the {@link Integer}, {@link Float}, {@link Long}, {@link Double},
	 * {@link String}, {@link Type} or {@link Handle} corresponding to
	 * the given constant pool item.
	 */
	public Object readConst(final int item, final char[] buf) {
		int index = items[item];
		switch (b[index - 1]) {
			case ClassWriter.INT:
				return readInt(index);
			case ClassWriter.FLOAT:
				return Float.intBitsToFloat(readInt(index));
			case ClassWriter.LONG:
				return readLong(index);
			case ClassWriter.DOUBLE:
				return Double.longBitsToDouble(readLong(index));
			case ClassWriter.CLASS:
				return Type.getObjectType(readUTF8(index, buf));
			case ClassWriter.STR:
				return readUTF8(index, buf);
			case ClassWriter.MTYPE:
				return Type.getMethodType(readUTF8(index, buf));
			default: // case ClassWriter.HANDLE_BASE + [1..9]:
				int tag = readByte(index);
				int[] items = this.items;
				int cpIndex = items[readUnsignedShort(index + 1)];
				boolean itf = b[cpIndex - 1] == ClassWriter.IMETH;
				String owner = readClass(cpIndex, buf);
				cpIndex = items[readUnsignedShort(cpIndex + 2)];
				String name = readUTF8(cpIndex, buf);
				String desc = readUTF8(cpIndex + 2, buf);
				return new Handle(tag, owner, name, desc, itf);
		}
	}
}
